/*!	 workarea.h
**	 Template Header
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**  Copyright (c) 2011 Nikita Kitaev
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifndef __SYNFIG_GTKMM_WORKAREA_H
#define __SYNFIG_GTKMM_WORKAREA_H

#include <list>
#include <map>
#include <set>

#include <ETL/smart_ptr>
#include <ETL/handle>

#include <gtkmm/drawingarea.h>
#include <gtkmm/table.h>
#include <gtkmm/adjustment.h>
#include <gtkmm/image.h>
#include <gdkmm/pixbuf.h>
#include <gdkmm/cursor.h>
#include <gdkmm/device.h>

#include <synfig/time.h>
#include <synfig/vector.h>
#include <synfig/renddesc.h>
#include <synfig/canvas.h>

#include "dials/zoomdial.h"
#include "widgets/widget_ruler.h"
#include "duckmatic.h"
#include "instance.h"
#include "app.h"

namespace synfigapp
{
class CanvasInterface;
};

namespace synfig
{
class Layer;
};
namespace Gtk
{
class Frame;
};

namespace studio
{
class WorkAreaTarget;
class WorkAreaTarget_Full;
class WorkAreaTarget_GL;

class Instance;
class CanvasView;
class WorkArea;
class WorkAreaRenderer;
class AsyncRenderer;

class DirtyTrap
{
    friend class WorkArea;
    WorkArea *work_area;
public:
    DirtyTrap(WorkArea *work_area);
    ~DirtyTrap();
};

class WorkAreaTile
{
public:
    typedef std::list<WorkAreaTile> List;

    int refresh_id;
    synfig::RectInt rect;
    Glib::RefPtr<Gdk::Pixbuf> pixbuf;
    cairo_surface_t* surface;

    WorkAreaTile(): refresh_id(), surface()
    { }
    WorkAreaTile(int refresh_id, int left, int top, const Glib::RefPtr<Gdk::Pixbuf> &pixbuf):
        refresh_id(refresh_id),
        rect(left,
             top,
             left + (pixbuf ? pixbuf->get_width() : 0),
             top + (pixbuf ? pixbuf->get_height() : 0)),
        pixbuf(pixbuf),
        surface() { }
    WorkAreaTile(int refresh_id, int left, int top, cairo_surface_t *surface):
        refresh_id(refresh_id),
        rect(left,
             top,
             left + (surface ? cairo_image_surface_get_width(surface) : 0),
             top + (surface ? cairo_image_surface_get_height(surface) : 0)),
        surface() { }

    bool operator< (const WorkAreaTile &other) const
    {
        return refresh_id < other.refresh_id;
    }
};

class WorkAreaTileBook
{
private:
    WorkAreaTile::List tiles;

public:
    const WorkAreaTile::List& get_tiles() const
    {
        return tiles;
    }

    WorkAreaTile* find_tile(int refresh_id, const synfig::RectInt &rect);

    void clear()
    {
        tiles.clear();
    }

    void sort();

    void add(const WorkAreaTile &tile);

    void add(int refresh_id, int left, int top, const Glib::RefPtr<Gdk::Pixbuf> &pixbuf)
    {
        add(WorkAreaTile(refresh_id, left, top, pixbuf));
    }
    void add(int refresh_id, int left, int top, cairo_surface_t *surface)
    {
        add(WorkAreaTile(refresh_id, left, top, surface));
    }

    void get_dirty_rects(
        std::vector<synfig::RectInt> &out_rects,
        int refresh_id,
        const synfig::RectInt &bounds,
        const synfig::VectorInt &max_size = synfig::VectorInt(INT_MAX, INT_MAX)) const;
};

class WorkArea : public Gtk::Table, public Duckmatic
{
    friend class WorkAreaTarget;
    friend class WorkAreaTarget_Full;
    friend class WorkAreaTarget_Cairo;
    friend class WorkAreaTarget_Cairo_Tile;
    friend class WorkAreaTarget_GL;
    friend class DirtyTrap;
    friend class WorkAreaRenderer;
    friend class WorkAreaProgress;



public:

    class PushState;
    friend class PushState;

    void insert_renderer(const etl::handle<WorkAreaRenderer> &x);
    void insert_renderer(const etl::handle<WorkAreaRenderer> &x, int priority);
    void erase_renderer(const etl::handle<WorkAreaRenderer> &x);
    void resort_render_set();

    enum DragMode {
        DRAG_NONE = 0,
        DRAG_WINDOW,
        DRAG_DUCK,
        DRAG_GUIDE,
        DRAG_BOX,
        DRAG_BEZIER,
        DRAG_ZOOM_WINDOW,
        DRAG_ROTATE_WINDOW
    };
    // Class used to store the cairo surface
    class SurfaceElement
    {
    public:
        cairo_surface_t* surface;
        int refreshes;
        SurfaceElement()
        {
            surface = NULL;
            refreshes = 0;
        }
        // Copy constructor
        SurfaceElement(const SurfaceElement& other): surface(cairo_surface_reference(other.surface)), refreshes(other.refreshes)
        {
        }
        ~SurfaceElement()
        {
            if (surface) {
                cairo_surface_destroy(surface);
            }
        }
    };

    typedef std::vector<SurfaceElement>	 SurfaceBook;



private:

    std::set<etl::handle<WorkAreaRenderer> > renderer_set_;

    etl::handle<studio::AsyncRenderer> async_renderer;

    etl::loose_handle<synfigapp::CanvasInterface> canvas_interface;
    etl::handle<synfig::Canvas> canvas;
    etl::loose_handle<studio::Instance> instance;
    etl::loose_handle<studio::CanvasView> canvas_view;

    // Widgets
    Gtk::DrawingArea *drawing_area;
    Glib::RefPtr<Gtk::Adjustment> scrollx_adjustment;
    Glib::RefPtr<Gtk::Adjustment> scrolly_adjustment;
    Widget_Ruler *vruler;
    Widget_Ruler *hruler;
    Gtk::Frame *drawing_frame;
    ZoomDial *zoomdial;

    GdkDevice* curr_input_device;

    // Bleh!
    int	w;						// < Width of the image (in pixels)
    int	h;						// < Height of the image (in pixels)
    synfig::Real	canvaswidth;	// < Width of the canvas
    synfig::Real	canvasheight;	// < Height of the canvas
    synfig::Real	pw;				// < The width of a pixel
    synfig::Real	ph;				// < The height of a pixel
    // float zoom and prev_zoom are declared in Duckmatic
    synfig::Point window_tl;		// < The (theoretical) top-left corner of the view window
    synfig::Point window_br;		// < The (theoretical) bottom-right corner of the view window

    guint32 last_event_time;

    int bpp;

    // ???
    synfig::ProgressCallback *progresscallback;

    // ???
    synfig::RendDesc desc;

    // This flag is set if the user is dragging the video window
    /*! \see drag_point */
    DragMode dragging;

    etl::handle<Duckmatic::Duck> clicked_duck;
    etl::handle<Duckmatic::Duck> hover_duck;

    // When dragging the viewport, this is set to the origin of the drag
    synfig::Point drag_point;

    synfig::Point curr_point;

    // ???
    synfig::Point previous_focus;

    // This flag is set if the grid should be drawn
    bool show_grid;

    // This flag is set if the guides should be drawn
    bool show_guides;

    // Checker background size
    synfig::Vector background_size;
    // Checker background first color
    synfig::Color background_first_color;
    // Checker background second color
    synfig::Color background_second_color;

    synfig::Time jack_offset;

    bool low_resolution;

    bool meta_data_lock;

    // This flag is set if the entire frame is rendered rather than using tiles
    bool full_frame;


    // This vector holds all of the tiles for this frame
    WorkAreaTileBook tile_book;

    // This integer describes the total times that the work area has been refreshed
    int refreshes;

    // This list holds the queue of tiles that need to be rendered

    int tile_w, tile_h;

    gint render_idle_func_id;

    // The coordinates of the focus the last time a part of the screen was refreshed
    synfig::Point last_focus_point;

    bool canceled_;

    int quality;
    int low_res_pixel_size;

    bool dirty_trap_enabled;

    int dirty_trap_queued;

    etl::loose_handle<synfig::ValueNode> selected_value_node_;

    bool allow_duck_clicks;
    bool allow_bezier_clicks;
    bool allow_layer_clicks;
    bool cancel;
    bool curr_guide_is_x;
    bool dirty;
    bool queued;
    bool rendering;

#ifdef SINGLE_THREADED
    /* resize bug workaround */
    int old_window_width;
    int old_window_height;
#endif



public:

    const etl::loose_handle<synfig::ValueNode>& get_selected_value_node()
    {
        return  selected_value_node_;
    }
    const synfig::Point& get_drag_point()const
    {
        return drag_point;
    }
    const WorkAreaTileBook& get_tile_book() const
    {
        return tile_book;
    }
    WorkAreaTileBook& get_tile_book()
    {
        return tile_book;
    }
    int get_refreshes()const
    {
        return refreshes;
    }
    bool get_canceled()const
    {
        return canceled_;
    }
    bool get_queued()const
    {
        return queued;
    }
    bool get_rendering()const
    {
        return rendering;
    }
#ifdef SINGLE_THREADED
    bool get_updating()const;
    void stop_updating(bool cancel = false);
#endif
    bool get_full_frame()const
    {
        return full_frame;
    }

    synfig::VectorInt get_windows_offset() const;
    synfig::RectInt get_window_rect(int stepx = 1, int stepy = 1) const;
    int get_tile_w()const
    {
        return tile_w;
    }
    int get_tile_h()const
    {
        return tile_h;
    }

    bool get_allow_layer_clicks()
    {
        return allow_layer_clicks;
    }
    void set_allow_layer_clicks(bool value)
    {
        allow_layer_clicks = value;
    }

    bool get_allow_duck_clicks()
    {
        return allow_duck_clicks;
    }
    void set_allow_duck_clicks(bool value)
    {
        allow_duck_clicks = value;
    }

    bool get_allow_bezier_clicks()
    {
        return allow_bezier_clicks;
    }
    void set_allow_bezier_clicks(bool value)
    {
        allow_bezier_clicks = value;
    }

    // used in renderer_ducks.cpp
    bool solid_lines;

    // used in renderer_guides.cpp
    GuideList::iterator curr_guide;

    // used in renderer_timecode.cpp
    int timecode_width, timecode_height;

    // used in renderer_bonesetup.cpp
    int bonesetup_width, bonesetup_height;



private:

    bool set_wh(int w, int h, int chan = 3);



private:

    sigc::signal<void, GdkDevice* > signal_input_device_changed_;

    // One signal per button
    sigc::signal<void, synfig::Point> signal_user_click_[5];

    sigc::signal<void> signal_popup_menu_;

    sigc::signal<void> signal_cursor_moved_;
    sigc::signal<void> signal_rendering_;

    // Signal for when the user clicks on a layer
    sigc::signal<void, etl::handle<synfig::Layer> > signal_layer_selected_;

    sigc::signal<void> signal_view_window_changed_;

    sigc::signal<void> signal_meta_data_changed_;

public:

    sigc::signal<void>& signal_rendering()
    {
        return signal_rendering_;
    }

    sigc::signal<void>& signal_cursor_moved()
    {
        return signal_cursor_moved_;
    }

    sigc::signal<void>& signal_view_window_changed()
    {
        return signal_view_window_changed_;
    }

    sigc::signal<void>& signal_meta_data_changed()
    {
        return signal_meta_data_changed_;
    }

    void view_window_changed()
    {
        signal_view_window_changed()();
    }

    sigc::signal<void, GdkDevice* >& signal_input_device_changed()
    {
        return signal_input_device_changed_;
    }

    sigc::signal<void> &signal_popup_menu()
    {
        return signal_popup_menu_;
    }

    // One signal per button (5 buttons)
    sigc::signal<void, synfig::Point> &signal_user_click(int button = 0)
    {
        return signal_user_click_[button];
    }

    sigc::signal<void, etl::handle<synfig::Layer> >& signal_layer_selected()
    {
        return signal_layer_selected_;
    }



public:
    void set_selected_value_node(etl::loose_handle<synfig::ValueNode> x);

    bool is_dragging()
    {
        return dragging != DRAG_NONE;
    }

    DragMode get_dragging_mode()
    {
        return dragging;
    }

    WorkArea(etl::loose_handle<synfigapp::CanvasInterface> canvas_interface);
    virtual ~WorkArea();

    void set_cursor(const Glib::RefPtr<Gdk::Cursor> &x);
    void set_cursor(Gdk::CursorType x);

    const synfig::Point& get_cursor_pos()const
    {
        return curr_point;
    }

    Glib::RefPtr<Gtk::Adjustment> get_scrollx_adjustment()
    {
        return scrollx_adjustment;
    }
    Glib::RefPtr<Gtk::Adjustment> get_scrolly_adjustment()
    {
        return scrolly_adjustment;
    }
    Glib::RefPtr<const Gtk::Adjustment> get_scrollx_adjustment() const
    {
        return scrollx_adjustment;
    }
    Glib::RefPtr<const Gtk::Adjustment> get_scrolly_adjustment() const
    {
        return scrolly_adjustment;
    }

    void set_instance(etl::loose_handle<studio::Instance> x)
    {
        instance = x;
    }
    void set_canvas(etl::handle<synfig::Canvas> x)
    {
        canvas = x;
    }
    void set_canvas_view(etl::loose_handle<studio::CanvasView> x)
    {
        canvas_view = x;
    }
    etl::handle<synfig::Canvas> get_canvas()const
    {
        return canvas;
    }
    etl::handle<studio::Instance> get_instance()const
    {
        return instance;
    }
    etl::loose_handle<studio::CanvasView> get_canvas_view()const
    {
        return canvas_view;
    }

    void refresh_dimension_info();

    // Enables showing of the grid
    void enable_grid();
    // Disables showing of the grid
    void disable_grid();
    // Toggles the showing of the grid
    void toggle_grid();
    // Returns the state of the show_grid flag
    bool grid_status()const
    {
        return show_grid;
    }
    // Toggles the snap of the grid
    void toggle_grid_snap();
    // Sets the size of the grid
    void set_grid_size(const synfig::Vector &s);
    // Sets the color of the grid
    void set_grid_color(const synfig::Color &c);
    // Returns the color of the grid
    const synfig::Color &get_grid_color()const
    {
        return Duckmatic::get_grid_color();
    }

    // Returns the state of the show_guides flag
    bool get_show_guides()const
    {
        return show_guides;
    }
    // Sets the showing of the grid
    void set_show_guides(bool x);
    // Toggles the showing of the guides
    void toggle_show_guides()
    {
        set_show_guides(!get_show_guides());
    }
    // Toggles the snap of the guides
    void toggle_guide_snap();
    // Sets the color of the guides
    void set_guides_color(const synfig::Color &c);
    // Returns the color of the guides
    const synfig::Color &get_guides_color()const
    {
        return Duckmatic::get_guides_color();
    }

    synfig::Time get_jack_offset()const
    {
        return jack_offset;
    }
    void set_jack_offset(const synfig::Time &x);

    // Sets the size of the checker background
    void set_background_size(const synfig::Vector &s);
    // Sets the first color of the checker background
    void set_background_first_color(const synfig::Color &c);
    // Sets the second color of the checker background
    void set_background_second_color(const synfig::Color &c);
    // Sets the size of the checker background
    const synfig::Vector &get_background_size()const
    {
        return background_size;
    }
    // Returns the first color of the checker background
    const synfig::Color &get_background_first_color()const
    {
        return background_first_color;
    }
    // Returns the second color of the checker background
    const synfig::Color &get_background_second_color()const
    {
        return background_second_color;
    }

    bool get_low_resolution_flag()const
    {
        return low_resolution;
    }
    void set_low_resolution_flag(bool x);
    void toggle_low_resolution_flag();

    // ???
    void queue_scroll();

    // ??
    void popup_menu();

    int get_quality()const
    {
        return quality;
    }
    int get_low_res_pixel_size()const
    {
        return low_res_pixel_size;
    }
    synfig::String get_renderer() const;

    void set_quality(int x);
    void set_low_res_pixel_size(int x);

    int get_w()const
    {
        return w;
    }
    int get_h()const
    {
        return h;
    }
    int get_bpp()const
    {
        return bpp;
    }

    // ??
    const synfig::RendDesc &get_rend_desc()const
    {
        return desc;
    }

    // ??
    void set_rend_desc(const synfig::RendDesc &x)
    {
        desc = x;
    }

    // Converts screen coords (ie: pixels) to composition coordinates
    synfig::Point screen_to_comp_coords(synfig::Point pos)const;

    // Converts composition coordinates to screen coords (ie: pixels)
    synfig::Point comp_to_screen_coords(synfig::Point pos)const;

    float get_pw()const
    {
        return pw;
    }
    float get_ph()const
    {
        return ph;
    }

    const synfig::Point &get_window_tl()const
    {
        return window_tl;
    }
    const synfig::Point &get_window_br()const
    {
        return window_br;
    }

    bool async_update_preview();
    void async_update_finished();
    void async_render_preview(synfig::Time time);
    void async_render_preview();

    bool sync_update_preview();
    bool sync_render_preview(synfig::Time time);
    bool sync_render_preview();
    void sync_render_preview_hook();

    void queue_render_preview();

    void queue_draw_preview();

    void zoom_in();
    void zoom_out();
    void zoom_fit();
    void zoom_norm();
    void zoom_edit();
    float get_zoom()const
    {
        return zoom;    // zoom is declared in Duckmatic
    }

    void set_zoom(float z);

    void set_progress_callback(synfig::ProgressCallback *x)
    {
        progresscallback = x;
    }
    synfig::ProgressCallback *get_progress_callback()
    {
        return progresscallback;
    }

    void set_focus_point(const synfig::Point &x);

    synfig::Point get_focus_point()const;

    void done_rendering();

#ifdef SINGLE_THREADED
    /* resize bug workaround */
    void refresh_second_check();
#endif
    bool refresh(const Cairo::RefPtr<Cairo::Context> &cr);

    void reset_cursor();
    void refresh_cursor();

    void save_meta_data();
    void load_meta_data();
    // Test initial meta data values
    bool have_meta_data();



private:
    bool on_key_press_event(GdkEventKey* event);
    bool on_key_release_event(GdkEventKey* event);
    bool on_drawing_area_event(GdkEvent* event);
    bool on_hruler_event(GdkEvent* event);
    bool on_vruler_event(GdkEvent* event);
    void on_duck_selection_single(const etl::handle<Duck>& duck_guid);



public:



private:

    static gboolean __render_preview(gpointer data);
#ifdef SINGLE_THREADED
    /* resize bug workaround */
    static gboolean __refresh_second_check(gpointer data);
#endif

}; // END of class WorkArea

/*! \class WorkArea::PushState
**	Saves the current duck view and editing options
**  Should be used by tools that hide ducks or change clickability settings */
class WorkArea::PushState
{
    WorkArea *workarea_;
    Type type_mask;
    bool allow_duck_clicks;
    bool allow_bezier_clicks;
    bool allow_layer_clicks;

    bool needs_restore;

public:
    PushState(WorkArea *workarea_);
    ~PushState();
    void restore();
}; // END of class WorkArea::PushState

}; // END of namespace studio

#endif
